/*
 * Scheduler CLASS:
 * Represents concept of scheduler/dispatcher maintaining routes within
 * the given graph lattice.
 *
 */

#ifndef UNITRAF_SCHEDULER_HPP
#define UNITRAF_SCHEDULER_HPP

#include <graph\Common.hpp>
#include <graph\Route.hpp>
#include <Exceptions.hpp>

// NO-PASS Penalty
#define _PENALTY 1000

namespace unitraf {

// Making aliases
using std::vector;

template<class _Graph>
  class Scheduler {

    typedef Scheduler _mysched;

   public:
    ///////////////////////////////////////////////////////////////////////////
    // PUBLIC INTERFACE

    typedef _Graph graph_type;
    
    // Type of route/path in given graph
    typedef Route<
              typename graph_type::vertex_descriptor > 
                route_type;

    // Type of list/set of routes
    typedef vector<route_type> routes_list_type;


    // CONSTRUCTORS AND INITIALIZERS

    Scheduler() : graph_(0) {};

    Scheduler(const graph_type& graph) { 
      _init(graph);
    };

    // DESTRUCTOR

    ~Scheduler() {
    };


    // METHODS

    void _init(const graph_type& graph) {
      if (!(graph_ = &graph)->_check_integrity())
        throw bad_graph("GRAPH SUPPLIED TO THE SCHEDULER ISN'T CONSISTENT!");
    };

    void _check() const { 
      // Check whether pointer to graph have been initialized
      assert(graph_); 
    };
    
    // Method to assess fitness value of particular set of routes, represented 
    // by the given iterators
    template<class _RouteDesc, class _FitTy>
      double _assess(const vector<_RouteDesc>& routes_indices_list, 
        vector<_FitTy>& routes_values) const { 
      //double _assess(const routes_list_type& routes_list) { 

        // Check for consistency
        _check();

        // Iterate through the sequence of routes
        for(typename vector<_RouteDesc>::const_iterator index = routes_indices_list.begin();
            index != routes_indices_list.end(); ++index) {
        
          typename routes_list_type::const_reference route = routes_list_[index->_n];

          // ####################################################################
          double time = 0.0, route_value = 0.0;

          // Iterate through particular route
          for(typename route_type::const_reverse_iterator last_station = 
              route._rbegin(), prev_station = last_station; 
              ++prev_station != route._rend(); ++last_station) {

            typename graph_type::model_type::vertex_vi psvi = 
              graph_->GetVertex(*prev_station);

            // Check for consistency
            assert(psvi.valid);

            typename graph_type::model_type::vertex_vi lsvi = 
              graph_->GetVertex(*last_station);

            // Check for consistency
            assert(lsvi.valid);

            typename graph_type::model_type::edge_vi road_section = 
              graph_->GetEdge(*prev_station, *last_station);

            // Check for consistency
            assert(road_section.valid);
              
            // Increment time-to-run
            (lsvi.self)->property_proxy.time += time;

            time += (road_section.self)->property_proxy.time;

            (psvi.self)->property_proxy.time += time;

            // ##################################################################
            route_value += 
              (psvi.self)->property_proxy.time * (psvi.self)->property_proxy.folks;

            // ##################################################################
            // Increment count of busses passing through this bus station
            (psvi.self)->property_proxy.buses += index->_size; 
            (lsvi.self)->property_proxy.buses += index->_size; 

          };

          // ####################################################################
          routes_values.push_back(_FitTy(index - routes_indices_list.begin(), route_value));

        };

        double average_time = 0.0;

        typename graph_type::model_type::vertex_pairii vertices = 
          graph_->GetVertices(); 

        for(; vertices.first != vertices.last; ++vertices.first) {
          // f * T / n 
		      if ((vertices.first)->property_proxy.buses)
                average_time += 
                  (vertices.first)->property_proxy.folks * (vertices.first)->property_proxy.time / \
                    (vertices.first)->property_proxy.buses;
		      else
		        average_time += _PENALTY;

          // ##################################################################
          (vertices.first)->property_proxy.reset();

        };


        return average_time;

      };

    // Method to assess total value of edges making up given route/path
    // represented by the pair of iterators
    template<class _Iter>
      double _assess_route(_Iter first, _Iter last) const {
        
        // Check for consistency
        _check();

        double value = 0.0;

        // Iterator `first` points to target vertex of some particular edge whilst
        // iterator `prev` points to source vertex of that edge
        for(_Iter prev = first++; first != last; ++first, ++prev) {
          
          typename graph_type::model_type::edge_vi edge = 
            graph_->GetEdge(*prev, *first);
          
          if (edge.valid)
            value += (edge.self)->property_proxy.weight;
          else
            assert("ROUTE CONTAINS INVALID EDGE!");

        };
    };

    // Finds all possible routes in the given graph's lattice
    template<class _Iter>
      void _find_routes(
        /* Iterator for the first vertex from the sequence */
        _Iter first,
        /* Iterator for the last vertex from the sequence */
        _Iter last,                             
        /* Final point of any root */
        typename graph_type::vertex_descriptor const& final_point,
        /* Route's length threshold value */
        const unsigned route_length_threshold) {

          // Check for consistency
          _check();

          Finder finder(final_point, route_length_threshold, *this);

          finder._find_routes(first, last, routes_list_);

      };


   private:
    /////////////////////////////////////////////////////////////////////////
    // PRIVATE INTERFACE
      
    // NESTED TYPES
    
    // Represents concept of all-possible-routes-finder method wrapped around
    // into the class concept (somewhat like closure)
    class Finder {

      public:
      /////////////////////////////////////////////////////////////////////////
      // PUBLIC INTERFACE

      Finder(typename graph_type::vertex_descriptor const& final_point, 
        const unsigned recurrence_threshold, const _mysched& scheduler) 
          : scheduler_(scheduler), final_point_(final_point), 
            recurrence_threshold_(recurrence_threshold) {};

      // METHODS

      // Finds all possible routes from given vertices to the ending point
      // based on the lattice of the given graph
      template<class _Iter>
        void _find_routes(_Iter first, _Iter last, routes_list_type& routes_list,
          const route_type& route = route_type()) {

            // Check for consistency
            scheduler_._check();

            for(; first != last; ++first) {
            
              route_type current_route(route);

              current_route._add(*first);

              typename graph_type::model_type::reachable_vertex_pairii pair = 
                scheduler_.graph_->GetReachableVertices(*first);

              if (current_route._length() < recurrence_threshold_ && pair.valid)
                _find_routes(pair.first, pair.last, routes_list, current_route);
              else
                if (*first == final_point_) 
                  routes_list.push_back(current_route);

            };

        };  // void _find_routes(...)

      /////////////////////////////////////////////////////////////////////////
      // REPRESENTATION

      // Graph to investigate lattice from
      const _mysched & scheduler_;

      // Final point of any root
      typename graph_type::vertex_descriptor final_point_;

      // Recurrence threshold
      const unsigned recurrence_threshold_;

    };  // class Finder
    

    /////////////////////////////////////////////////////////////////////////
    // REPRESENTATION
    
    // Graph to get lattice from
    const graph_type * graph_;

    // All applicable routes
    routes_list_type routes_list_;

  };

};  // namespace unitraf

#endif  // #ifndef UNITRAF_SCHEDULER_HPP
